package org.farng.mp3.filename;

import org.farng.mp3.MP3File;
import org.farng.mp3.TagException;
import org.farng.mp3.TagOptionSingleton;
import org.farng.mp3.TagUtility;
import org.farng.mp3.id3.ID3v2_4;

import java.util.Iterator;


/**
 * This class builds a <code>FilenameTag</code>. The main method to call is
 * <code>createFilenameTagFromMP3File</code>. Other methods are public in
 * order to update parts of the tag if needed.
 *
 * @author Eric Farng
 * @version $Revision: 1.11 $
 */
public class FilenameTagBuilder {
    /**
     * Creates a FilenameComposite tree with the given string. It is parsed
     * according to the different values that can be set in
     * <code>TagOptionSingleton</code> class
     *
     * @param token filename to parse
     *
     * @return FilenameComposite tree representing the given token.
     *
     * @throws TagException is thrown if there are unmatched parenthesis
     */
    static public AbstractFilenameComposite createCompositeFromToken(String token)
        throws TagException {
        String[]                  splitToken = null;
        AbstractFilenameComposite composite = null;
        AbstractFilenameComposite beforeComposite;
        AbstractFilenameComposite middleComposite;
        AbstractFilenameComposite afterComposite;

        splitToken = parseParenthesis(token);

        if (splitToken != null) {
            composite = new FilenameParenthesis();
            ((FilenameParenthesis) composite).setOpenDelimiter(splitToken[0]);

            //((FilenameParenthesis) composite).setCloseDelimiter(splitToken[1]);
            beforeComposite = createCompositeFromToken(splitToken[2]);
            ((FilenameParenthesis) composite).setBeforeComposite(beforeComposite);

            middleComposite = createCompositeFromToken(splitToken[3]);
            ((FilenameParenthesis) composite).setMiddleComposite(middleComposite);

            afterComposite = createCompositeFromToken(splitToken[4]);
            ((FilenameParenthesis) composite).setAfterComposite(afterComposite);

            composite.setOriginalToken(token);

            return composite;
        }

        splitToken = parseDelimiter(token);

        if (splitToken != null) {
            composite = new FilenameDelimiter();
            ((FilenameDelimiter) composite).setDelimiter(splitToken[0]);
            beforeComposite = createCompositeFromToken(splitToken[1]);
            ((FilenameDelimiter) composite).setBeforeComposite(beforeComposite);
            afterComposite = createCompositeFromToken(splitToken[2]);
            ((FilenameDelimiter) composite).setAfterComposite(afterComposite);
            composite.setOriginalToken(token);

            return composite;
        }

        splitToken = parseStartWordDelimiter(token);

        if (splitToken != null) {
            composite = new FilenameStartWordDelimiter();
            ((FilenameDelimiter) composite).setDelimiter(splitToken[0]);

            beforeComposite = createCompositeFromToken(splitToken[1]);
            ((FilenameStartWordDelimiter) composite).setBeforeComposite(beforeComposite);

            afterComposite = createCompositeFromToken(splitToken[2]);
            ((FilenameStartWordDelimiter) composite).setAfterComposite(afterComposite);
            composite.setOriginalToken(token);

            return composite;
        }

        splitToken = parseEndWordDelimiter(token);

        if (splitToken != null) {
            composite = new FilenameEndWordDelimiter();

            ((FilenameDelimiter) composite).setDelimiter(splitToken[0]);

            beforeComposite = createCompositeFromToken(splitToken[1]);
            ((FilenameEndWordDelimiter) composite).setBeforeComposite(beforeComposite);
            afterComposite = createCompositeFromToken(splitToken[2]);
            ((FilenameEndWordDelimiter) composite).setAfterComposite(afterComposite);
            composite.setOriginalToken(token);

            return composite;
        }

        if ((token != null) && (token.trim().length() > 0)) {
            composite = new FilenameToken();
            ((FilenameToken) composite).setToken(token.trim());
            composite.setOriginalToken(token);

            return composite;
        }

        return null;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    static public FilenameTag createEmptyFilenameTag() {
        FilenameTag filenameTag = new FilenameTag();
        filenameTag.setId3tag(new ID3v2_4());

        return filenameTag;
    }

    /**
     * This method will create a complete FilenameTag from the given mp3File
     * and from the options in <code>TagOptionSingleton</code>. This will call
     * all other necessary methods in this builder class.
     *
     * @param mp3File MP3 file to create the FilenameTag from.
     *
     * @return FilenameTag of the mp3File argument
     *
     * @throws Exception is thrown on any IO errors, or parsing errors such as
     *         unmatched parenthesis
     */
    static public FilenameTag createFilenameTagFromMP3File(MP3File mp3File)
                                                    throws Exception {
        if (mp3File.getMp3file() == null) {
            return null;
        }

        FilenameTag               filenameTag = new FilenameTag();
        AbstractFilenameComposite composite;
        ID3v2_4                   id3tag;
        String                    filename = mp3File.getMp3file().getName();

        int                       index = filename.lastIndexOf('.');

        if (index >= 0) {
            filenameTag.setExtension(filename.substring(index + 1));
            filename = filename.substring(0, index);
        }

        // create composite
        composite = createCompositeFromToken(filename);
        updateCompositeFromAllTag(composite, mp3File);
        updateCompositeFromAllOption(composite);

        // create tag
        id3tag = composite.createId3Tag();

        // assign values;
        filenameTag.setMp3file(mp3File);
        filenameTag.setComposite(composite);
        filenameTag.setId3tag(id3tag);

        return filenameTag;
    }

    /**
     * Create an ID3v2_4 tag from the given composite. Each
     * <code>FilenameToken</code> in the composite is used to create a field
     * in the new tag. This means that the ID3v2 class field needs to be set
     * otherwise the token will be skipped. A dupilicate class/field/token
     * will replace the original.
     *
     * @param composite composite used to create the new tag
     */

//    static public ID3v2_4 createId3TagFromComposite(FilenameComposite composite)
//                                             throws Exception {
//        Class         classname;
//        FilenameToken token;
//        ID3v2_4       tag      = new ID3v2_4();
//        Iterator      iterator = composite.iterator();
//
//        while (iterator.hasNext()) {
//            token     = (FilenameToken) iterator.next();
//            classname = token.getId3v2FrameBodyClass();
//
//            if (classname != null) {
//                AbstractID3v2FrameBody body = (AbstractID3v2FrameBody) classname.newInstance();
//
//                /**
//                 * @todo need to add support for more frame bodies here
//                 */
//                if (body instanceof AbstractFrameBodyTextInformation) {
//                    ((AbstractFrameBodyTextInformation) body).setText(token.getToken());
//                    ((AbstractFrameBodyTextInformation) body).setTextEncoding((byte) 0);
//                } else if (body instanceof AbstractFrameBodyUrlLink) {
//                    ((AbstractFrameBodyUrlLink) body).setUrlLink(token.getToken());
//                }
//
//                ID3v2_4Frame frame = new ID3v2_4Frame();
//                frame.setBody(body);
//                tag.setFrame(frame);
//            }
//        }
//
//        return composite.createId3Tag();
//    }

    /**
     * Traverse the composite and set the class field to match keywords found
     * in TagOptionSingleton.
     *
     * @param composite composite to update.
     */
    static public void updateCompositeFromAllOption(AbstractFilenameComposite composite) {
        Iterator iterator = TagOptionSingleton.getInstance().getKeywordIterator();

        while (iterator.hasNext()) {
            composite.matchAgainstKeyword((Class) iterator.next());
        }
    }

    /**
     * Traverse the composite and set the class field to match frames from all
     * three other tags that are already found in the MP3 file.
     *
     * @param composite composite to update
     * @param mp3File mp3file to match all it's tags against.
     */
    static public void updateCompositeFromAllTag(AbstractFilenameComposite composite, MP3File mp3File) {
        composite.matchAgainstTag(mp3File.getID3v1Tag());
        composite.matchAgainstTag(mp3File.getID3v2Tag());
        composite.matchAgainstTag(mp3File.getLyrics3Tag());
    }

    /**
     * Parses the given token into two halves with the delimiters found in
     * <code> TagOptionSingleton</code>
     *
     * @param token token to split
     *
     * @return index 0 is the delimiter. index 1 and 2 are the before and after
     *         tokens respectively.
     */
    static private String[] parseDelimiter(String token) {
        String[] tokenArray = null;

        if ((token != null) && (token.length() > 0)) {
            Iterator iterator = TagOptionSingleton.getInstance().getFilenameDelimiterIterator();
            int      index;
            String   delimiter;

            while (iterator.hasNext()) {
                delimiter = (String) iterator.next();
                index     = token.indexOf(delimiter);

                if (index >= 0) {
                    tokenArray    = new String[3];
                    tokenArray[0] = delimiter;
                    tokenArray[1] = token.substring(0, index);
                    tokenArray[2] = token.substring(index + delimiter.length());
                }
            }
        }

        return tokenArray;
    }

    /**
     * Parses the given token into two halves with the delimiters found in
     * <code> TagOptionSingleton</code>
     *
     * @param token token to split
     *
     * @return index 0 is the delimiter. index 1 and 2 are the before and after
     *         tokens respectively.
     */
    static private String[] parseEndWordDelimiter(String token) {
        String[] tokenArray = null;

        if ((token != null) && (token.length() > 0)) {
            Iterator iterator = TagOptionSingleton.getInstance().getEndWordDelimiterIterator();
            int      index;
            String   delimiter;

            while (iterator.hasNext()) {
                delimiter = (String) iterator.next();

                if (token.endsWith(delimiter)) {
                    index = token.substring(0, token.length() - delimiter.length()).indexOf(delimiter);
                } else {
                    index = token.indexOf(delimiter);
                }

                if (index > 0) {
                    tokenArray    = new String[3];
                    tokenArray[0] = delimiter;
                    tokenArray[1] = token.substring(0, index);
                    tokenArray[2] = token.substring(index);
                }
            }
        }

        return tokenArray;
    }

    /**
     * Given a specific token, parse it into halves according to the
     * <code>TagOptionSingleton</code>
     *
     * @param token token to split.
     *
     * @return index 0 and 1 are the parenthesis delimiters. index 2, 3, 4 are
     *         before, middle, and after respectively.
     *
     * @throws TagException DOCUMENT ME!
     */
    static private String[] parseParenthesis(String token)
                                      throws TagException {
        String[] tokenArray = null;

        if ((token != null) && (token.length() > 0)) {
            TagOptionSingleton option = TagOptionSingleton.getInstance();
            String             tempOpen = "";
            String             open = "";
            String             close = "";
            int                openIndex = token.length();
            int                tempIndex;
            int                closeIndex = -1;
            Iterator           iterator = option.getOpenParenthesisIterator();

            // find first parenthesis
            while (iterator.hasNext()) {
                tempOpen  = (String) iterator.next();
                tempIndex = token.indexOf(tempOpen);

                if ((tempIndex >= 0) && (tempIndex < openIndex)) {
                    openIndex = tempIndex;
                    open      = tempOpen;
                }
            }

            // we have a parenthesis
            if ((openIndex >= 0) && (openIndex < token.length())) {
                close      = option.getCloseParenthesis(open);
                closeIndex = TagUtility.findMatchingParenthesis(token, openIndex);

                if (closeIndex < 0) {
                    throw new TagException("Unmatched parenthesis in \"" + token + "\" at position : " + openIndex);
                }

                tokenArray    = new String[5];
                tokenArray[0] = open;
                tokenArray[1] = close;
                tokenArray[2] = token.substring(0, openIndex);
                tokenArray[3] = token.substring(openIndex + open.length(), closeIndex);
                tokenArray[4] = token.substring(closeIndex + close.length());
            }
        }

        return tokenArray;
    }

    /**
     * Parses the given token into two halves with the delimiters found in
     * <code> TagOptionSingleton</code>
     *
     * @param token token to split
     *
     * @return index 0 is the delimiter. index 1 and 2 are the before and after
     *         tokens respectively.
     */
    static private String[] parseStartWordDelimiter(String token) {
        String[] tokenArray = null;

        if ((token != null) && (token.length() > 0)) {
            Iterator iterator = TagOptionSingleton.getInstance().getStartWordDelimiterIterator();
            int      index;
            String   delimiter;

            while (iterator.hasNext()) {
                delimiter = (String) iterator.next();

                if (token.startsWith(delimiter)) {
                    index = token.indexOf(delimiter, delimiter.length());
                } else {
                    index = token.indexOf(delimiter);
                }

                if (index > 0) {
                    tokenArray    = new String[3];
                    tokenArray[0] = delimiter;
                    tokenArray[1] = token.substring(0, index);
                    tokenArray[2] = token.substring(index);
                }
            }
        }

        return tokenArray;
    }
}